package com.jf.anmocker.plugin.mockcore

import com.jf.mocker.anotations.MethodMockType
import javassist.*

/**
 * @Class: MethodMocker
 * @Description:
 * @author:
 * @Date: 2022/9/27
 */
class MemberInjectMocker(var mClassPool : ClassPool) : IMethodMockAble{

    companion object {
        const val MOCK_FILE_NAME = "mocker"
    }

    fun modifyMethod(targetCls : CtClass, method : CtMethod, anoCls : CtClass, anoList : MutableList<MethodAnoEntity> ) : Boolean {
        var modifiedFlag = false
        //添加本地变量
        addMockerClassFiledToCls(targetCls, anoCls)
        println("MethodMocker >>> ${targetCls.simpleName} [${method.name}] check ${anoList.size}")
        anoList.forEach {
            println("MethodMocker >>> modifyMethod ${it.methodMocker.name} : ${it.methodMocker.mockType}")
            when(it.methodMocker.mockType){
                MethodMockType.Before -> {
                    modifiedFlag = insertBefore(targetCls, method, it)
                }
                MethodMockType.After -> {
                    modifiedFlag = insertAfter(targetCls, method, it)
                }
                MethodMockType.Replace -> {
                    modifiedFlag = replace(targetCls, method, it)
                }
                else -> println("unknown method mock-type : ${it.methodMocker.mockType}")
            }
        }
        //执行mock
        return modifiedFlag
    }

    override fun modifyMethod(targetCls : CtClass, method : CtMethod, ano : MethodAnoEntity ) : Boolean {
        var modifiedFlag = false
        //添加本地变量
        addMockerClassFiledToCls(targetCls, ano.anoClz)
        println("MethodMocker >>> modifyMethod ${ano.methodMocker.name} : ${ano.methodMocker.mockType}")
        when(ano.methodMocker.mockType){
            MethodMockType.Before -> {
                modifiedFlag = insertBefore(targetCls, method, ano)
            }
            MethodMockType.After -> {
                modifiedFlag = insertAfter(targetCls, method, ano)
            }
            MethodMockType.Replace -> {
                modifiedFlag = replace(targetCls, method, ano)
            }
            else -> println("unknown method mock-type : ${ano.methodMocker.mockType}")
        }
        //执行mock
        return modifiedFlag
    }

    private fun addMockerClassFiledToCls(targetCls : CtClass, anoCls : CtClass){
        var field : CtField? = null
        try {
            field = targetCls.getDeclaredField(MOCK_FILE_NAME)
        }catch (e : NotFoundException){
            println("class[${targetCls.simpleName}] filed[${MOCK_FILE_NAME}] not exits ")
        }
        if(field == null){
            var ctConstructor : CtConstructor? = null
            try{
                ctConstructor = anoCls.getDeclaredConstructor(arrayOf(targetCls))
            }catch (e : NotFoundException){
                println("class[${anoCls.simpleName}] Constructor[${targetCls.simpleName}] not exits ")
            }
            if(ctConstructor == null){
                //添加空参数构造成员变量
                mClassPool.importPackage(anoCls.packageName)
                val code = "${anoCls.simpleName} $MOCK_FILE_NAME = new ${anoCls.simpleName}();"
                field = CtField.make(code, targetCls)
                field.modifiers = Modifier.PRIVATE
                targetCls.addField(field)
            }else{
                field = CtField(anoCls, MOCK_FILE_NAME, targetCls)
                targetCls.addField(field, CtField.Initializer.byNew(anoCls))
            }
            println("class[${targetCls.simpleName}] add filed[${MOCK_FILE_NAME}]")
        }
    }

    override fun insertBefore(targetCls : CtClass, method : CtMethod, ano : MethodAnoEntity) : Boolean{
        try {
            method.insertBefore("{$0.$MOCK_FILE_NAME.${ano.method.name}();}")
            println("method[${method.name}] insertBefore perform")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    override fun insertAfter(targetCls : CtClass, method : CtMethod, ano : MethodAnoEntity) : Boolean{
        try {
            method.insertAfter("{$0.$MOCK_FILE_NAME.${ano.method.name}();}")
            println("method[${method.name}] insertBefore perform")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

    override fun replace(targetCls : CtClass, method : CtMethod, ano : MethodAnoEntity) : Boolean{
        try {
            method.setBody(ano.method, null)
            println("method[${method.name}] setBody perform")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return false
    }

}

